/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Michael Houston <schmeeky@gmail.com> - initial API and implementation
 *    Tom Schindl <tom.schindl@bestsolution.at> - bug fix in: 191216
 *******************************************************************************/
package org.eclipse.nebula.jface.gridviewer;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerRow;
import org.eclipse.nebula.widgets.grid.Grid;
import org.eclipse.nebula.widgets.grid.GridItem;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Widget;


/**
 * A concrete viewer based on an Grid control.
 * <p>
 * This class is not intended to be subclassed outside the viewer framework. It is designed to be
 * instantiated with a pre-existing Grid control and configured with a domain-specific content
 * provider, label provider, element filter (optional), and element sorter (optional).
 * <p>
 * Content providers for grid tree viewers must implement the {@link ITreeContentProvider}
 * interface.
 * <p>
 * <b>The current implementation does not support lazy content providers.</b>
 * </p>
 */
public class GridTreeViewer extends AbstractTreeViewer {

  /** This viewer's grid control. */
  private Grid grid;
  private GridViewerRow cachedRow;
  /**
   * If true, this grid viewer will ensure that the grid's rows / GridItems are always sized to
   * their preferred height.
   */
  private boolean autoPreferredHeight = false;
  private CellLabelProvider rowHeaderLabelProvider;

  /**
   * Creates a grid tree viewer on a newly-created grid control under the given parent. The grid
   * control is created using the SWT style bits <code>MULTI, H_SCROLL, V_SCROLL,</code> and
   * <code>BORDER</code>. The viewer has no input, no content provider, a default label provider, no
   * sorter, and no filters.
   *
   * @param parent the parent control
   */
  public GridTreeViewer( Composite parent ) {
    this( parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER );
  }

  /**
   * Creates a grid tree viewer on a newly-created grid control under the given parent. The grid
   * control is created using the given SWT style bits. The viewer has no input, no content
   * provider, a default label provider, no sorter, and no filters.
   *
   * @param parent the parent control
   * @param style the SWT style bits used to create the grid.
   */
  public GridTreeViewer( Composite parent, int style ) {
    this( new Grid( parent, style ) );
  }

  /**
   * Creates a grid tree viewer on the given grid control. The viewer has no input, no content
   * provider, a default label provider, no sorter, and no filters.
   *
   * @param grid the grid control
   */
  public GridTreeViewer( Grid grid ) {
    this.grid = grid;
    hookControl( grid );
  }

  /**
   * Returns the underlying {@link Grid} Control.
   *
   * @return grid control.
   */
  public Grid getGrid() {
    return grid;
  }

  /** {@inheritDoc} */
  @Override
  protected Item getItemAt( Point point ) {
    return grid.getItem( point );
  }

  /** {@inheritDoc} */
  @Override
  protected ColumnViewerEditor createViewerEditor() {
    return new GridViewerEditor( this,
                                 new ColumnViewerEditorActivationStrategy( this ),
                                 ColumnViewerEditor.DEFAULT );
  }

  /** {@inheritDoc} */
  @Override
  protected void addTreeListener( Control control, TreeListener listener ) {
    ( ( Grid )control ).addTreeListener( listener );
  }

  /** {@inheritDoc} */
  @Override
  protected Item[] getChildren( Widget o ) {
    if( o instanceof GridItem ) {
      return ( ( GridItem )o ).getItems();
    }
    if( o instanceof Grid ) {
      return ( ( Grid )o ).getRootItems();
    }
    return null;
  }

  /** {@inheritDoc} */
  @Override
  protected boolean getExpanded( Item item ) {
    return ( ( GridItem )item ).isExpanded();
  }

  /** {@inheritDoc} */
  @Override
  protected int getItemCount( Control control ) {
    return ( ( Grid )control ).getItemCount();
  }

  /** {@inheritDoc} */
  @Override
  protected int getItemCount( Item item ) {
    return ( ( GridItem )item ).getItemCount();
  }

  /** {@inheritDoc} */
  @Override
  protected Item[] getItems( Item item ) {
    return ( ( GridItem )item ).getItems();
  }

  /** {@inheritDoc} */
  @Override
  protected Item getParentItem( Item item ) {
    return ( ( GridItem )item ).getParentItem();
  }

  /** {@inheritDoc} */
  @Override
  protected Item[] getSelection( Control control ) {
    return ( ( Grid )control ).getSelection();
  }

  /** {@inheritDoc} */
  @Override
  protected Item newItem( Widget parent, int style, int index ) {
    GridItem item;
    if( parent instanceof GridItem ) {
      item = ( GridItem )createNewRowPart( getViewerRowFromItem( parent ), style, index ).getItem();
    } else {
      item = ( GridItem )createNewRowPart( null, style, index ).getItem();
    }
    return item;
  }

  /**
   * Create a new ViewerRow at rowIndex
   *
   * @param parent the parent row
   * @param style the style bits to use for the new row
   * @param rowIndex the index at which the new row should be created under the parent
   * @return ViewerRow the new row
   */
  private ViewerRow createNewRowPart( ViewerRow parent, int style, int rowIndex ) {
    if( parent == null ) {
      if( rowIndex >= 0 ) {
        return getViewerRowFromItem( new GridItem( grid, style, rowIndex ) );
      }
      return getViewerRowFromItem( new GridItem( grid, style ) );
    }
    if( rowIndex >= 0 ) {
      return getViewerRowFromItem( new GridItem( ( GridItem )parent.getItem(),
                                                 SWT.NONE,
                                                 rowIndex ) );
    }
    return getViewerRowFromItem( new GridItem( ( GridItem )parent.getItem(), SWT.NONE ) );
  }

  /** {@inheritDoc} */
  @Override
  protected void removeAll( Control control ) {
    ( ( Grid )control ).removeAll();
  }

  /** {@inheritDoc} */
  @Override
  protected void setExpanded( Item item, boolean expand ) {
    ( ( GridItem )item ).setExpanded( expand );
  }

  /** {@inheritDoc} */
  @Override
  protected void setSelection( List items ) {
    Item[] current = getSelection( getGrid() );
    // Don't bother resetting the same selection
    if( isSameSelection( items, current ) ) {
      return;
    }
    GridItem[] newItems = new GridItem[ items.size() ];
    items.toArray( newItems );
    getGrid().setSelection( newItems );
    getGrid().showSelection();
  }

  /** {@inheritDoc} */
  @Override
  protected void showItem( Item item ) {
    getGrid().showItem( ( GridItem )item );
  }

  /** {@inheritDoc} */
  @Override
  public Control getControl() {
    return getGrid();
  }

  /** {@inheritDoc} */
  @Override
  protected ViewerRow getViewerRowFromItem( Widget item ) {
    if( cachedRow == null ) {
      cachedRow = new GridViewerRow( ( GridItem )item );
    } else {
      cachedRow.setItem( ( GridItem )item );
    }
    return cachedRow;
  }

  /** {@inheritDoc} */
  @Override
  protected Widget getColumnViewerOwner( int columnIndex ) {
    if( columnIndex < 0 || ( columnIndex > 0 && columnIndex >= getGrid().getColumnCount() ) ) {
      return null;
    }
    if( getGrid().getColumnCount() == 0 ) {
      return getGrid();
    }
    return getGrid().getColumn( columnIndex );
  }

  /**
   * Returns the number of columns of this viewer.
   *
   * @return the number of columns
   */
  @Override
  protected int doGetColumnCount() {
    return grid.getColumnCount();
  }

  /**
   * When set to true, this grid viewer will ensure that each of the grid's items is always
   * automatically sized to its preferred height. The default is false.
   * <p>
   * Since this mechanism usually leads to a grid with rows of different heights and thus to a grid
   * with decreased performance, it should only be applied if that is intended. To set the height of
   * all items to a specific value, use {@link Grid#setItemHeight(int)} instead.
   * <p>
   * When a column with activated word wrapping is resized by dragging the column resizer, the items
   * are only auto-resized properly if you use {@link GridViewerColumn} to create the columns.
   * <p>
   * When this method is called, existing rows are not resized to their preferred height. Therefore
   * it is suggested that this method be called before rows are populated (i.e. before setInput).
   */
  public void setAutoPreferredHeight( boolean autoPreferredHeight ) {
    this.autoPreferredHeight = autoPreferredHeight;
  }

  /**
   * @return true if this grid viewer sizes its rows to their preferred height
   * @see #setAutoPreferredHeight(boolean)
   */
  public boolean getAutoPreferredHeight() {
    return autoPreferredHeight;
  }

  /** {@inheritDoc} */
  @Override
  protected void doUpdateItem( final Item item, Object element ) {
    super.doUpdateItem( item, element );
    updateRowHeader( item );
    if( autoPreferredHeight && !item.isDisposed() ) {
      ( ( GridItem )item ).pack();
    }
  }

  /**
   * Removes the element at the specified index of the parent. The selection is updated if required.
   *
   * @param parentOrTreePath the parent element, the input element, or a tree path to the parent
   *          element
   * @param index child index
   * @since 3.3
   */
  public void remove( final Object parentOrTreePath, final int index ) {
    if( checkBusy() ) {
      return;
    }
    final List oldSelection = new LinkedList( Arrays
      .asList( ( ( TreeSelection )getSelection() ).getPaths() ) );
    preservingSelection( new Runnable() {

      public void run() {
        TreePath removedPath = null;
        if( internalIsInputOrEmptyPath( parentOrTreePath ) ) {
          Grid tree = ( Grid )getControl();
          if( index < tree.getItemCount() ) {
            GridItem item = tree.getItem( index );
            if( item.getData() != null ) {
              removedPath = getTreePathFromItem( item );
              disassociate( item );
            }
            item.dispose();
          }
        } else {
          Widget[] parentItems = internalFindItems( parentOrTreePath );
          for( int i = 0; i < parentItems.length; i++ ) {
            GridItem parentItem = ( GridItem )parentItems[ i ];
            if( parentItem.isDisposed() ) {
              continue;
            }
            if( index < parentItem.getItemCount() ) {
              GridItem item = parentItem.getItem( index );
              if( item.getData() != null ) {
                removedPath = getTreePathFromItem( item );
                disassociate( item );
              }
              item.dispose();
            }
          }
        }
        if( removedPath != null ) {
          boolean removed = false;
          for( Iterator it = oldSelection.iterator(); it.hasNext(); ) {
            TreePath path = ( TreePath )it.next();
            if( path.startsWith( removedPath, getComparer() ) ) {
              it.remove();
              removed = true;
            }
          }
          if( removed ) {
            setSelection( new TreeSelection( ( TreePath[] )oldSelection
              .toArray( new TreePath[ oldSelection.size() ] ), getComparer() ), false );
          }
        }
      }
    } );
  }

  /**
   * Label provider used by calculate the row header text
   *
   * @param rowHeaderLabelProvider the provider
   */
  public void setRowHeaderLabelProvider( CellLabelProvider rowHeaderLabelProvider ) {
    this.rowHeaderLabelProvider = rowHeaderLabelProvider;
  }

  private void updateRowHeader( Widget widget ) {
    if( rowHeaderLabelProvider != null ) {
      ViewerCell cell = getViewerRowFromItem( widget ).getCell( Integer.MAX_VALUE );
      rowHeaderLabelProvider.update( cell );
    }
  }

}
